﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using GraphAPI.Model;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace GraphAPI
{
	public delegate void DeleteConnectionHandler(Connection c);

	public partial class Connection : Control
	{
		protected Pen pen;

		private Relation relation;

		public Relation Relation
		{
			get { return relation; }
		}

		public Connection(Vertex start, Vertex end, Relation relation)
		{
			SetStyle(ControlStyles.SupportsTransparentBackColor, true);
			SetStyle(ControlStyles.Opaque, true);

			//SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

			pen = new Pen(this.ForeColor);

			this.start = start;
			start.Move += new EventHandler(node_Move);
			start.DeleteVertex += new DeleteVertexHandler(vertexDeleted);
			end.Move += new EventHandler(node_Move);
			end.DeleteVertex += new DeleteVertexHandler(vertexDeleted);
			this.end = end;

			this.relation = relation;

			this.oriented = relation.Oriented;

			this.Text = relation.Name;

			this.Weight = relation.Weight;

			this.BackColor = Color.Transparent;

			if (relation.ColorMark == Color.Empty)
				this.ForeColor = Color.Black;
			else
				this.ForeColor = relation.ColorMark;

			pen = new Pen(this.ForeColor);

			AdjustSize();
		}

		void vertexDeleted(Vertex v)
		{
			Delete();
		}

		public event DeleteConnectionHandler DeleteConnection;

		public void Delete()
		{
			if (DeleteConnection != null)
				DeleteConnection(this);
		}

		protected bool oriented;

		public bool Oriented
		{
			get { return oriented; }
			set
			{
				oriented = value;
				relation.Oriented = oriented;
				AdjustSize();
			}
		}

		protected override CreateParams CreateParams
		{
			get
			{
				CreateParams cp = base.CreateParams;
				cp.ExStyle |= 0x20;
				return cp;
			}
		}

		void node_Move(object sender, EventArgs e)
		{
			AdjustSize();
		}

		protected Vertex start;

		public Vertex Start
		{
			get { return start; }
			set
			{
				start = value;
				AdjustSize();
			}
		}

		protected Vertex end;

		public Vertex End
		{
			get { return end; }
			set
			{
				end = value;
				AdjustSize();
			}
		}

		private void InvalidateEx()
		{
			if (Parent == null)
				return;

			Rectangle rc = new Rectangle(this.Location, this.Size);
			Parent.Invalidate(rc, true);
		}

		private Color textColor = Color.Black;

		[Category("Appearance")]
		public Color TextColor
		{
			get { return textColor; }
			set
			{
				textColor = value;
				InvalidateEx();
			}
		}

		public string Commentary
		{
			get { return relation.Commentary; }
			set { relation.Commentary = value; }
		}

		public double Weight
		{
			get { return relation.Weight; }
			set
			{
				relation.Weight = value;
				AdjustSize();
			}
		}

		protected override void OnTextChanged(EventArgs e)
		{
			relation.Name = this.Text;
			base.OnTextChanged(e);

			AdjustSize();
		}

		private string FullCaption
		{
			get
			{
				if (relation.Weight.Equals(double.NaN))
					return this.Text;
				else
					return this.Text + " (" + relation.Weight.ToString() + ")";
			}
		}

		private SizeF GetCaptionSize(Graphics g)
		{
			return g.MeasureString(this.FullCaption, this.Font);
		}

		private PointF GetStart()
		{
			float width = end.Center.X - start.Center.X;
			float height = end.Center.Y - start.Center.Y;
			float length = (float)Math.Sqrt(width * width + height * height);
			float radius = start.NodeSize;

			float dx = width * radius / length;
			float dy = height * radius / length;

			return new PointF(start.Center.X + dx, start.Center.Y + dy);
		}

		private PointF GetEnd()
		{
			float width = start.Center.X - end.Center.X;
			float height = start.Center.Y - end.Center.Y;
			float length = (float)Math.Sqrt(width * width + height * height);
			float radius = end.NodeSize;

			float dx = width * radius / length;
			float dy = height * radius / length;

			return new PointF(end.Center.X + dx, end.Center.Y + dy);
		}

		PointF defaultArrowTop = new PointF(-10.0f, 5.0f);
		PointF defaultArrowBottom = new PointF(-10.0f, -5.0f);

		PointF arrowTop = new PointF();
		PointF arrowBottom = new PointF();

		private PointF AlignToConnection(PointF p)
		{
			PointF startCorner = GetStart();
			PointF endCorner = GetEnd();
//			SizeF from = new SizeF(startCorner.X - this.Left, startCorner.Y - this.Top);
//			PointF to = new PointF(endCorner.X - this.Left, endCorner.Y - this.Top);

//			PointF vec = PointF.Subtract(to, from);
			PointF vec = PointF.Subtract(endCorner, new SizeF(startCorner));

			float length = (float)Math.Sqrt(vec.X * vec.X + vec.Y * vec.Y);

			float cosa = vec.X / length;

			float a = (float)Math.Acos(cosa);
			if (vec.Y < 0)
				a = (float)Math.PI * 2 - a;

			PointF newPoint = new PointF(p.X * cosa - p.Y * (float)Math.Sin(a),
										p.Y * cosa + p.X * (float)Math.Sin(a));

//			return PointF.Add(newPoint, new SizeF(to));
			return PointF.Add(newPoint, new SizeF(endCorner));
		}

		public void ChangeDirection()
		{
			Vertex tmp = end;
			end = start;
			start = tmp;

			AdjustSize();
		}

		protected virtual void AdjustSize()
		{
			PointF startCorner = GetStart();
			PointF endCorner = GetEnd();

			float width = Math.Abs(endCorner.X - startCorner.X);
			float height = Math.Abs(endCorner.Y - startCorner.Y);

			width += 4;
			height += 4;

			Graphics g = CreateGraphics();
			SizeF captionSize = GetCaptionSize(g);

			width = Math.Max(width, (int)Math.Round((float)captionSize.Width));
			height = Math.Max(height, (int)Math.Round((float)captionSize.Height));

			float centerX = (startCorner.X + endCorner.X) / 2.0f;
			float centerY = (startCorner.Y + endCorner.Y) / 2.0f;

			float newLeft = centerX - width / 2.0f;
			float newTop = centerY - height / 2.0f;
			float newRight = newLeft + width;
			float newBottom = newTop + height;

			if (oriented)
			{
				arrowTop = AlignToConnection(defaultArrowTop);
				arrowBottom = AlignToConnection(defaultArrowBottom);

				newLeft = Math.Min(newLeft, arrowTop.X);
				newLeft = Math.Min(newLeft, arrowBottom.X);

				newTop = Math.Min(newTop, arrowTop.Y);
				newTop = Math.Min(newTop, arrowBottom.Y);

				newRight = Math.Max(newRight, arrowTop.X);
				newRight = Math.Max(newRight, arrowBottom.X);

				newBottom = Math.Max(newBottom, arrowTop.Y);
				newBottom = Math.Max(newBottom, arrowBottom.Y);

				width = newRight - newLeft;
				height = newBottom - newTop;
			}

			this.SetBounds(	(int)Math.Floor(newLeft), 
							(int)Math.Floor(newTop),
							(int)Math.Ceiling(newRight) - (int)Math.Floor(newLeft) + 1,
							(int)Math.Ceiling(newBottom) - (int)Math.Floor(newTop) + 1);
			
			PointF from = new PointF(startCorner.X - this.Left, startCorner.Y - this.Top);
			PointF to = new PointF(endCorner.X - this.Left, endCorner.Y - this.Top);

			GraphicsPath path = new GraphicsPath();
			path.StartFigure();
			if (this.Width > this.Height)
			{
				path.AddLine(from.X, from.Y + 2, to.X, to.Y + 2);
				path.AddLine(to.X, to.Y + 2, to.X, to.Y - 2);
				path.AddLine(to.X, to.Y - 2, from.X, from.Y - 2);
				path.AddLine(from.X, from.Y - 2, from.X, from.Y + 2);
				path.CloseFigure();
			}
			else
			{
				path.AddLine(from.X + 2, from.Y, to.X + 2, to.Y);
				path.AddLine(to.X + 2, to.Y, to.X - 2, to.Y);
				path.AddLine(to.X - 2, to.Y, from.X - 2, from.Y);
				path.AddLine(from.X - 2, from.Y, from.X + 2, from.Y);
				path.CloseFigure();
			}

			Region r = new Region(path);

			if (FullCaption.Length > 0)
			{
				r.Union(new RectangleF(	(this.Width - captionSize.Width) / 2.0f,
										(this.Height - captionSize.Height) / 2.0f,
										captionSize.Width,
										captionSize.Height));
			}

			if (oriented)
			{
				arrowTop = new PointF(arrowTop.X - this.Left, arrowTop.Y - this.Top);
				arrowBottom = new PointF(arrowBottom.X - this.Left, arrowBottom.Y - this.Top);

				float left = to.X;
				left = Math.Min(left, arrowTop.X);
				left = Math.Min(left, arrowBottom.X);

				float top = to.Y;
				top = Math.Min(top, arrowTop.Y);
				top = Math.Min(top, arrowBottom.Y);

				float right = to.X;
				right = Math.Max(right, arrowTop.X);
				right = Math.Max(right, arrowBottom.X);

				float bottom = to.Y;
				bottom = Math.Max(bottom, arrowTop.Y);
				bottom = Math.Max(bottom, arrowBottom.Y);

				r.Union(new Rectangle(
							(int)Math.Floor(left), 
							(int)Math.Floor(top),
							(int)Math.Ceiling(right) - (int)Math.Floor(left) + 1,
							(int)Math.Ceiling(bottom) - (int)Math.Floor(top) + 1));
			}

			this.Region = r;
//			this.Region = new Region();

			SendToBack();

			InvalidateEx();
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			e.Graphics.SmoothingMode = SmoothingMode.HighQuality;

//			e.Graphics.FillRegion(Brushes.Red, this.Region);

			PointF startCorner = GetStart();
			PointF endCorner = GetEnd();
			PointF from = new PointF(startCorner.X - this.Left, startCorner.Y - this.Top);
			PointF to = new PointF(endCorner.X - this.Left, endCorner.Y - this.Top);
			e.Graphics.DrawLine(pen, from, to);

			if (oriented)
			{
				e.Graphics.DrawLine(pen, arrowTop, to);
				e.Graphics.DrawLine(pen, arrowBottom, to);
			}

			float centerX = (from.X + to.X) / 2.0f;
			float centerY = (from.Y + to.Y) / 2.0f;

			SizeF captionSize = GetCaptionSize(e.Graphics);

			e.Graphics.DrawString(this.FullCaption, this.Font, new SolidBrush(textColor),
									centerX - captionSize.Width / 2.0f, centerY - captionSize.Height / 2.0f);

			base.OnPaint(e);
		}

	}
}
